package com.dsteiner.xelavos.server;

import static com.dsteiner.xelavos.client.controller.IHistoryToken.HT_OBJECT_ID;
import static com.dsteiner.xelavos.client.controller.IHistoryToken.HT_SELECTED_TAB;
import static com.dsteiner.xelavos.client.controller.IHistoryToken.HT_SHOW_MESSAGE_BOX;
import static com.google.appengine.api.labs.taskqueue.TaskOptions.Builder.url;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Logger;

import com.dsteiner.widgets.client.DateUtilImpl;
import com.dsteiner.widgets.client.ObjectUtil;
import com.dsteiner.widgets.client.StringUtil;
import com.dsteiner.widgets.client.SuggestBoxWidget.Suggestion;
import com.dsteiner.widgets.client.SuggestBoxWidget.SuggestionRequest;
import com.dsteiner.widgets.client.SuggestBoxWidget.SuggestionResult;
import com.dsteiner.widgets.client.domain.DataRequest;
import com.dsteiner.widgets.client.domain.Distinct;
import com.dsteiner.widgets.client.domain.ModelObject;
import com.dsteiner.widgets.client.domain.SearchableRefId;
import com.dsteiner.widgets.client.domain.SearchableValue;
import com.dsteiner.widgets.client.domain.Sort;
import com.dsteiner.widgets.client.event.DataUpdateEvent;
import com.dsteiner.widgets.client.event.EventListener;
import com.dsteiner.widgets.client.event.EventManager;
import com.dsteiner.widgets.client.event.EventManagerImpl;
import com.dsteiner.widgets.client.rpc.CrudResult;
import com.dsteiner.widgets.client.rpc.SearchRequest;
import com.dsteiner.widgets.client.rpc.SearchResult;
import com.dsteiner.widgets.client.rpc.SearchRequest.SearchRequestImpl;
import com.dsteiner.widgets.server.BCrypt;
import com.dsteiner.widgets.server.BaseDataServiceImpl;
import com.dsteiner.widgets.server.ServerUtil;
import com.dsteiner.widgets.server.dao.Parameter;
import com.dsteiner.widgets.server.util.CacheUtil;
import com.dsteiner.widgets.server.util.CacheUtil.CacheUtilMap;
import com.dsteiner.xelavos.client.constants.Constants;
import com.dsteiner.xelavos.client.controller.PlaceController;
import com.dsteiner.xelavos.client.domain.Comment;
import com.dsteiner.xelavos.client.domain.Commentable;
import com.dsteiner.xelavos.client.domain.DataResult;
import com.dsteiner.xelavos.client.domain.HasUser;
import com.dsteiner.xelavos.client.domain.Job;
import com.dsteiner.xelavos.client.domain.Leisure;
import com.dsteiner.xelavos.client.domain.Product;
import com.dsteiner.xelavos.client.domain.Rateable;
import com.dsteiner.xelavos.client.domain.Rating;
import com.dsteiner.xelavos.client.domain.SearchInfo;
import com.dsteiner.xelavos.client.domain.Service;
import com.dsteiner.xelavos.client.domain.TrackComments;
import com.dsteiner.xelavos.client.domain.User;
import com.dsteiner.xelavos.client.domain.UserStats;
import com.dsteiner.xelavos.client.model.DataModel;
import com.dsteiner.xelavos.client.model.MainTabModel;
import com.dsteiner.xelavos.client.model.MainTabModel.Tab;
import com.dsteiner.xelavos.client.rpc.DataService;
import com.dsteiner.xelavos.server.dao.DataDao;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;
import com.google.appengine.api.xmpp.JID;
import com.google.appengine.api.xmpp.Message;
import com.google.appengine.api.xmpp.MessageBuilder;

/**
 * The server side implementation of the RPC service.
 */
@SuppressWarnings("serial")
public class DataServiceImpl extends BaseDataServiceImpl implements DataService {

	private DataDao dataDao = DataDao.dataDao;
	private Queue queue = QueueFactory.getQueue("mail-queue");
	private static final Logger log = Logger.getLogger(DataService.class.getName());
	
	private CacheUtil cacheUtil = CacheUtilMap.getInstance();
	private EventManager eventManager;

	public DataServiceImpl() {
		setDataDao(dataDao);
		setEventManager(EventManagerImpl.getInstance());
	}
	
	private void setEventManager(EventManager eventManager) {
		this.eventManager = eventManager;
		eventManager.registerListener(DataUpdateEvent.class, new EventListener<DataUpdateEvent>() {
			public void onEvent(DataUpdateEvent event) {
				Object object = event.getObject();
				List topCacheValues = (List) cacheUtil.getCacheValue("top", object.getClass());
				if(topCacheValues != null && topCacheValues.contains(object)){
					topCacheValues.set(topCacheValues.indexOf(object), object);
					cacheUtil.setCacheValue("top", object.getClass(), topCacheValues);
				}
				List newCacheValues = (List) cacheUtil.getCacheValue("new", object.getClass());
				if(newCacheValues != null && newCacheValues.contains(object)){
					newCacheValues.set(newCacheValues.indexOf(object), object);
					cacheUtil.setCacheValue("new", object.getClass(), newCacheValues);
				}
				SearchResult defaultSearchResult = (SearchResult) cacheUtil.getCacheValue("default", object.getClass().getName());
				if(defaultSearchResult != null && defaultSearchResult.getObjects() != null && defaultSearchResult.getObjects().contains(object)){
					defaultSearchResult.getObjects().set(defaultSearchResult.getObjects().indexOf(object), object);
					cacheUtil.setCacheValue("default", object.getClass().getName(), defaultSearchResult);
				}
			}
		});
	}

	public void setDataDao(DataDao dataDao) {
		this.dataDao = dataDao;
		super.setDataDao(dataDao);
	}
	
	public void clearCache() {
		cacheUtil.clear();
	}

	public CrudResult createModelObject(ModelObject modelObject) {
		CrudResult crudResult = new CrudResult();
		try{
			String errorMsg = "";
			if(modelObject instanceof Rating){
				Rating rating = (Rating) modelObject;
				List<Parameter> parametersMap = new ArrayList<Parameter>();
				parametersMap.add(new Parameter("userIdParam", rating.getUserId()));
				parametersMap.add(new Parameter("refIdParam", rating.getRefId()));
				boolean exist = dataDao.isExistant(Rating.class, parametersMap);
				if(exist)
					errorMsg += Constants.MSG_OBJ_ALREADY_RATED;
			}
			else if(modelObject instanceof TrackComments){
				TrackComments trackComments = (TrackComments) modelObject;
				List<Parameter> parametersMap = new ArrayList<Parameter>();
				parametersMap.add(new Parameter("objectIdParam", trackComments.getObjectId()));
				parametersMap.add(new Parameter("userIdParam", trackComments.getUserId()));
				List<TrackComments> trackCommentsResults = dataDao.getObjects(TrackComments.class, parametersMap);
				if(trackCommentsResults != null && !trackCommentsResults.isEmpty()){
					errorMsg += Constants.MSG_OBJ_ALREADY_TRACKED;
					trackComments.setId(trackCommentsResults.get(0).getId());
					crudResult.setModelObject(trackComments);
				}
			}
			else if(modelObject instanceof User){
				User user = (User) modelObject;
				List<Parameter> parametersMap = new ArrayList<Parameter>();
				parametersMap.add(new Parameter("emailParam", user.getEmail()));
				boolean emailRegistered = dataDao.isExistant(User.class, parametersMap);
				if(emailRegistered)
					errorMsg += Constants.MSG_EMAIL_ALREADY_REGISTERED;
				else{
					user.setValidated(false);
					String hash = BCrypt.hashpw(user.getPassword(), BCrypt.gensalt());
					user.setPassword(hash);
					User savedObject = (User) dataDao.save(user);
					sendConfirmationMail(savedObject);
					errorMsg += Constants.MSG_CONFIRMATION_EMAIL_SENT;
				}
			}
			else if(modelObject instanceof Leisure){
				checkLeisureObject((Leisure) modelObject);
			}
	
			if(!errorMsg.equals("")){
				crudResult.setError(errorMsg);
				return crudResult;
			}
			
			CrudResult createModelObject = super.createModelObject(modelObject);
			ModelObject savedObj = createModelObject.getModelObject();
			
			if(savedObj instanceof Rateable){
				cacheUtil.removeCacheValue("new", savedObj.getClass());
				cacheUtil.removeCacheValue("default", savedObj.getClass().getName());
			}
			if(savedObj instanceof HasUser){
				HasUser hasUser = (HasUser) savedObj;
				String userId = hasUser.getUserId();
				User user = (User) dataDao.getObjectById(userId, User.class);
				user.setEntriesCount(user.getEntriesCount()+1);
				dataDao.updateObject(user);
			}
			if(savedObj instanceof Rating){
				Rating rating = (Rating) savedObj;
				Class clazz = Class.forName(rating.getRefClass());
				Rateable rateable = (Rateable) dataDao.getObjectById(rating.getRefId(), clazz);
				rateable.addRating(rating.getRating());
				dataDao.updateObject((ModelObject)rateable);
			}
			if(savedObj instanceof Comment){
				Comment comment = (Comment) savedObj;
				Class clazz = Class.forName(comment.getRefClass());
				Commentable commentable = (Commentable) dataDao.getObjectById(comment.getRefId(), clazz);
				commentable.addCommentCount(1);
				dataDao.updateObject((ModelObject)commentable);
				List<Parameter> parametersList = new ArrayList<Parameter>();
				parametersList.add(new Parameter("objectIdParam", comment.getRefId()));
				List<TrackComments> trackCommentsResult = dataDao.getObjects(TrackComments.class, parametersList);
				for (TrackComments trackComments : trackCommentsResult) {
					Queue queue = QueueFactory.getQueue("mail-queue");
					StringBuffer htmlTextSb = new StringBuffer();
					StringBuffer plainTextSb = new StringBuffer();
					EmailFormatter.format(trackComments, comment, (ModelObject) commentable, plainTextSb, htmlTextSb);
					queue.add(url("/tasks/email")
							.param("userId", trackComments.getUserId())
							.param("subject", "Un nuevo comentario fue agregado al registro "+commentable.toString())
							.param("htmlText", htmlTextSb.toString())
							.param("plainText", plainTextSb.toString()));
				}
			}
			
			crudResult.setModelObject(savedObj);
		}catch (Exception e) {
			e.printStackTrace();
			crudResult.setError(e.getMessage());
		}
		return crudResult;
	}

	private void checkLeisureObject(Leisure leisure) {
		Date eventDateAsDate = leisure.getEventDateAsDate();
		if(eventDateAsDate != null){
			eventDateAsDate.setHours(0);
			eventDateAsDate.setMinutes(0);
			eventDateAsDate.setSeconds(0);
			leisure.setEventDateAsDate(eventDateAsDate);
		}
		Date eventDateUntilAsDate = leisure.getEventDateUntilAsDate();
		if(eventDateUntilAsDate != null){
			eventDateUntilAsDate.setHours(0);
			eventDateUntilAsDate.setMinutes(0);
			eventDateUntilAsDate.setSeconds(0);
			leisure.setEventDateUntilAsDate(eventDateUntilAsDate);
		}
	}

	public CrudResult deleteObject(ModelObject modelObject) {
		super.deleteObject(modelObject);
		
		if(modelObject instanceof HasUser){
			String userId = ((HasUser)modelObject).getUserId();
			User user = (User) dataDao.getObjectById(userId, User.class);
			user.setEntriesCount(user.getEntriesCount()-1);
			dataDao.updateObject((ModelObject)user);
		}
		if(modelObject instanceof Rateable){
			cacheUtil.removeCacheValue("new", modelObject.getClass());
			cacheUtil.removeCacheValue("default", modelObject.getClass().getName());
		}
		return new CrudResult();
	}

	public DataResult getData(List<DataRequest> dataRequests) {
		try{
			DataResult dataResult = new DataResult();
			for (DataRequest dataRequest : dataRequests) {
				if(dataRequest.equals(DataModel.DATAREQUEST_POLL)){
					Object user = dataRequest.getParameterObj("user");
					if(user == null)
						user = getThreadLocalRequest().getSession().getId();
					cacheUtil.setCacheValue("access", user, new Date());
	
					String type = dataRequest.getParameters("type");
					Class modelObjectClass = null;
					try {
						modelObjectClass = Class.forName(type);
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
					String orderingTop = null;
					String orderingNew = null;
					if(type.equals(User.class.getName())){
						orderingTop = "entriesCount desc";
						orderingNew = "joinDate desc";
					}
					else if(type.equals(Product.class.getName())){
						orderingTop = "rating desc, ratingCount desc";
						orderingNew = "registeredDate desc";
					}
					else if(type.equals(Job.class.getName())){
						orderingTop = "rating desc, ratingCount desc";
						orderingNew = "registeredDate desc";
					}
					else if(type.equals(Leisure.class.getName())){
						orderingTop = "rating desc, ratingCount desc";
						orderingNew = "registeredDate desc";
					}
					else if(type.equals(Service.class.getName())){
						orderingTop = "rating desc, ratingCount desc";
						orderingNew = "registeredDate desc";
					}
					List<ModelObject> itemsTop = (List<ModelObject>) cacheUtil.getCacheValue("top", modelObjectClass);
					if(itemsTop == null){
						itemsTop = dataDao.getTopModelObjects(modelObjectClass, orderingTop);
						cacheUtil.setCacheValue("top", modelObjectClass, itemsTop);
					}
					List<ModelObject> itemsNew = (List<ModelObject>) cacheUtil.getCacheValue("new", modelObjectClass);
					if(itemsNew == null){
						itemsNew = dataDao.getTopModelObjects(modelObjectClass, orderingNew);
						cacheUtil.setCacheValue("new", modelObjectClass, itemsNew);
					}
					dataResult.setItemsTop(itemsTop);
					dataResult.setItemsNew(itemsNew);
					
					UserStats userStats = getUserStats();
					dataResult.setUserStats(userStats);
				}
				else if(dataRequest.equals(DataModel.DATAREQUEST_SEARCH)){
					SearchRequest searchRequest = (SearchRequest) dataRequest.getParameterObj("searchRequest");
					SearchResult<ModelObject> searchResult = null;
					
					boolean isSearchRequestDefault = DataModel.SEARCH_REQUEST_DEFAULT.getType() == searchRequest.getType() 
					&& ObjectUtil.equals(DataModel.SEARCH_REQUEST_DEFAULT.getSort(), searchRequest.getSort())
					&& DataModel.SEARCH_REQUEST_DEFAULT.getOffset() == searchRequest.getOffset()
					&& DataModel.SEARCH_REQUEST_DEFAULT.getLimit() == searchRequest.getLimit()
					&& ObjectUtil.equals(DataModel.SEARCH_REQUEST_DEFAULT.getText(), searchRequest.getText());
					
					if(isSearchRequestDefault)
						searchResult = (SearchResult<ModelObject>) cacheUtil.getCacheValue("default", searchRequest.getClassName());
					
					if(searchResult == null)
						searchResult = dataDao.search(searchRequest);
					
					if(isSearchRequestDefault)
						cacheUtil.setCacheValue("default", searchRequest.getClassName(), searchResult);
					
					if(searchResult.getCount() == 0){
						SearchInfo searchInfo = getSearchInfo(searchRequest);
						dataResult.setSearchInfo(searchInfo);
					}
					dataResult.setSearchResults(searchResult);
				}
				else if(dataRequest.equals(DataModel.DATAREQUEST_MY_ENTRIES)){
					String userid = dataRequest.getParameters("userid");
					Integer offset = new Integer(dataRequest.getParameters("offset"));
					Integer limit = new Integer(dataRequest.getParameters("limit"));
					SearchRequestImpl searchRequest = new SearchRequestImpl();
					searchRequest.setText(userid);
					searchRequest.setAnnotationClassName(SearchableRefId.class.getName());
					searchRequest.setSort(new Sort("registeredDate",false));
					searchRequest.setLimit(9999);
					SearchResult<ModelObject> resultMyEntries = new SearchResult<ModelObject>();
					
					searchRequest.setClassName(Product.class.getName());
					resultMyEntries.add(dataDao.search(searchRequest));
					searchRequest.setClassName(Leisure.class.getName());
					resultMyEntries.add(dataDao.search(searchRequest));
					searchRequest.setClassName(Service.class.getName());
					resultMyEntries.add(dataDao.search(searchRequest));
					searchRequest.setClassName(Job.class.getName());
					resultMyEntries.add(dataDao.search(searchRequest));
					
					List<ModelObject> objectList = resultMyEntries.getObjects();
					List<ModelObject> subList = ObjectUtil.subList(objectList, offset.intValue(), offset + limit);
					resultMyEntries.setObjects(subList);
					resultMyEntries.setCount(objectList.size());
					dataResult.setMyEntries(resultMyEntries);
				}
			}
	//		System.out.println("DataServiceImpl.getData() with:"+dataRequests);
			return dataResult;
		}catch(Exception ex){
			ex.printStackTrace();
		}
		return null;
	}

	public CrudResult<User> getUser(String email, String password) {
		try{
			CrudResult<User> result = new CrudResult<User>();
			SearchResult<User> search = searchUser(email);
			if(search.getCount() == 1){
				List<User> objects = search.getObjects();
				User user = objects.get(0);
				if(user.isValidated() != null && !user.isValidated()){
					result.setError(Constants.MSG_VALIDATE_ACCOUNT);
					result.setModelObject(user);
					return result;
				}
				
				boolean valid = false;
				try{
					valid = BCrypt.checkpw(password, user.getPassword());
				}catch(Exception ex){
				}
				
				if(valid){
					cacheUtil.setCacheValue("login", user, new Date());
					result.setModelObject(user);
				}
				else{
					result.setError(Constants.MSG_PASSWORD_NOT_CORRECT);
				}
			}
			else if(search.getCount() == 0){
				result.setError(Constants.Util.getMsgEmailNotExistant(email));
			}
			else if(search.getCount()  > 1){
				result.setError(Constants.MSG_INSERT_FULL_EMAIL);
			}
			return result;
		}catch(Exception ex){
			ex.printStackTrace();
		}
		return null;
	}

	public UserStats getUserStats(){
		try{
			UserStats userStats = new UserStats();
			int countLoggedIn = 0;
			int countOnline = 0;
			User lastLoggedIn = null;
			Date lastLoggedInDate = null;
	
			long nowInMs = new Date().getTime();
			Map<User, Date> loginMap = (Map<User, Date>) cacheUtil.getCache("login");
			for (User user : loginMap.keySet()) {
				Date loginDate = (Date) loginMap.get(user);
				if(lastLoggedIn == null || loginDate.after(lastLoggedInDate)){
					lastLoggedInDate = loginDate;
					lastLoggedIn = user;
				}
			}
			
			Map<Object, Date> accessMap = (Map<Object, Date>) cacheUtil.getCache("access");
			for (Object user : accessMap.keySet()) {
				Date lastAccessDate = (Date) accessMap.get(user);
				boolean isOnline = lastAccessDate.getTime() > nowInMs - Constants.POLL_DATA_MS - 10000;
				if(isOnline){
					countOnline++;
					if(user instanceof User)
						countLoggedIn++;
				}
			}
			
			userStats.setCountLoggedIn(countLoggedIn);
			userStats.setCountOnline(countOnline);
			userStats.setLastLoggedIn(lastLoggedIn);
			
			return userStats;
		}catch(Exception ex){
			ex.printStackTrace();
		}
		return null;
	}

	public void sendConfirmationMail(User user) {
		log.info("send confirmation mail to "+user.getEmail());
		try {
	        String userId = user.getId();
			String msgTextBody = "Estimado "+user.getFirstName()+",\n\n " +
	        		"muchas gracias por su registracion en XelaVos.com.\n " +
	        		"Para completar su registracion en XelaVos.com, estar logeado y poder ingresar nuevas informaciones " +
	        		"por favor visite el siguiente link \n " +
	        getRegistrationLink(userId);

            String subject = user.getFirstName()+", su registracion en XelaVos.com";
            log.info("send confirmation mail with link xelavos/registration?uid="+userId+" to "+user.getEmail());
			queue.add(url("/tasks/email")
					.param("email", user.getEmail())
					.param("subject", subject)
					.param("htmlText", StringUtil.toHtml(msgTextBody))
					.param("plainText", msgTextBody));
            
            log.info("user saved with id:"+userId);
            log.info("confirmation mail sent");
        } catch (Exception e) {
        	e.printStackTrace();
        	log.warning(e.getMessage());
        }		
	}

	private String getRegistrationLink(String userId) {
		return "http://www.xelavos.com/xelavos/registration?uid="+userId;
	}

	public String sendPassword(String email) {
		SearchResult<User> search = searchUser(email);
		if(search.getCount() == 1){
			User user = search.getObjects().get(0);
			String randomPwd = UUID.randomUUID().toString().substring(0, 10);
			user.setPassword(BCrypt.hashpw(randomPwd, BCrypt.gensalt()));
			dataDao.updateObject((ModelObject)user);
			String msgTextBody = "Su nueva contraseña para ingresar a www.Xelavos.com es:\n"+randomPwd
			+"\n\nHaga click en el siguiente link para logearse "+ getRegistrationLink(user.getId())
			+"\n\nPuede cambiar esa contraseña despues de logearse haciendo click en 'mi perfil'";
			log.info("send new password "+randomPwd+" to "+user.getEmail());
			queue.add(url("/tasks/email")
					.param("email", user.getEmail())
					.param("subject", user.getFirstName()+", su contraseña para www.XelaVos.com")
					.param("htmlText", StringUtil.toHtml(msgTextBody))
					.param("plainText", msgTextBody));
			return "su contraseña ha sido enviado a "+user.getEmail();
		}
		else if(search.getCount() == 0){
			return "email '"+email+"' no existe";
		}
		else if(search.getCount() > 1){
			return Constants.MSG_INSERT_FULL_EMAIL; 
		}
		return null;
	}

	public void setQueue(Queue queue) {
		this.queue = queue;
	}

	public void setCacheUtil(CacheUtil cacheUtil) {
		this.cacheUtil = cacheUtil;
	}
	
	private SearchResult<User> searchUser(String email) {
		SearchRequestImpl searchRequest = new SearchRequestImpl();
		searchRequest.setLimit(5);
		searchRequest.setType(SearchRequest.TYPE_PREFIX);
		searchRequest.setClassName(User.class.getName());
		searchRequest.setAnnotationClassName(SearchableValue.class.getName());
		searchRequest.setText(email);
		SearchResult<User> search = dataDao.search(searchRequest);
		return search;
	}
	
	public SearchInfo getSearchInfo(SearchRequest searchRequest){
		SearchInfo searchInfo = new SearchInfo();
		String classNameOrig = searchRequest.getClassName();
		searchRequest.setOffset(0);
		searchRequest.setLimit(10);
		Map<Tab, SearchResult<ModelObject>> tabSearchResults = new HashMap<Tab, SearchResult<ModelObject>>();
		for (int i = 0; i < MainTabModel.tabs.length; i++) {
			Tab tab = MainTabModel.tabs[i];
			if(tab.getClassName().equals(classNameOrig))
				continue;

			searchRequest.setClassName(tab.getClassName());
			SearchResult searchResult = dataDao.search(searchRequest);
			if(searchResult.getCount() > 0){
				tabSearchResults.put(tab, searchResult);
			}
		}
		searchInfo.setTabSearchResults(tabSearchResults);
		
		String text = searchRequest.getText();
		if(text != null){
			if(text.length() > 3){
				SuggestionRequest request = new SuggestionRequest();
				request.setText(text.substring(0,3));
				request.setClassName(classNameOrig);
				request.setDisplayCount(10);
				SuggestionResult suggestions = getSuggestions(request);
				if(!suggestions.getSuggestions().isEmpty())
					searchInfo.setStartWithSuggestions(suggestions.getSuggestions());
			}
			
			String[] split = text.split("\\s");
			List<Suggestion> wordSuggestions = new ArrayList<Suggestion>();
			if(split.length > 1){
				for (int i = 1; i < split.length; i++) {
					SuggestionRequest request = new SuggestionRequest();
					request.setText(split[i].length() > 3 ? split[i].substring(0,3) : split[i]);
					request.setClassName(classNameOrig);
					request.setDisplayCount(10);
					SuggestionResult suggestions = getSuggestions(request);
					if(!suggestions.getSuggestions().isEmpty())
						wordSuggestions.addAll(suggestions.getSuggestions());
				}
			}
			searchInfo.setWordSuggestions(wordSuggestions);
		}
		
		return searchInfo;
	}

	public String sendMessage(String fromUserId, String toUserId, String message) {
		log.info("send message");
		User toUser = dataDao.getObjectById(toUserId, User.class);
		User fromUser = dataDao.getObjectById(fromUserId, User.class);
		JID jid = new JID(toUser.getEmail());
        String msgBody = fromUser.getFirstName()+" te ha mandado este mensaje: "+message;
        Message msg = new MessageBuilder()
            .withRecipientJids(jid)
            .withBody(msgBody)
            .build();
                
        boolean messageSent = false;
//        XMPPService xmpp = XMPPServiceFactory.getXMPPService();
//        log.info("send invitation to "+jid);
        
//        xmpp.sendInvitation(jid);
//        if (xmpp.getPresence(jid).isAvailable()) {
//            SendResponse status = xmpp.sendMessage(msg);
//            messageSent = (status.getStatusMap().get(jid) == SendResponse.Status.SUCCESS);
//    		log.info("send message '"+msgBody+"' via XMPP with messageSent:"+messageSent);
//        }

        if (!messageSent) {
    		String fromUserUrl = "<a href=\""+getRegistrationLink(toUserId)+
    		"#"+PlaceController.getHistoryToken(
    				HT_SELECTED_TAB.setValue(MainTabModel.getTab(fromUser).getId()), 
    				HT_OBJECT_ID.setValue(fromUser.getId()),
    				HT_SHOW_MESSAGE_BOX.setValue(fromUser))+"\">en ese link</a>";

        	msgBody += "\n\n<strong>No responder a ese correo</strong>, si quiere contestar a "+fromUser.getFirstName()
        	+" haga click "+fromUserUrl;
        	log.info("send message '"+msgBody+"' via email");
			queue.add(url("/tasks/email")
					.param("email", toUser.getEmail())
					.param("subject", fromUser.getFirstName()+" te ha mandado un mensaje")
					.param("htmlText", StringUtil.toHtml(msgBody))
					.param("plainText", msgBody));
        }		
        return null;
	}

	public CrudResult<User> updatePassword(User user, String password) {
		String hashpw = BCrypt.hashpw(password, BCrypt.gensalt());
		user.setPassword(hashpw);
		return updateObject(user);
	}
	
	@Override
	public CrudResult updateObject(ModelObject modelObject) {
		if(modelObject instanceof Leisure){
			checkLeisureObject((Leisure) modelObject);
		}
		return super.updateObject(modelObject);
	}
}
